From 53b63ac27c2275262db9a04be02210a3287aa71d Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 1 Dec 2023 19:34:58 +0100 Subject: refactor(pages): refine Blog pages * replace usePostsList with useArticlesList to keep names coherent * remove useIsMounted hook * rewrite useRedirection hook * add redirect in getStaticProps to avoid unecessary fetching * move Pagination component in a noscript tag * use hooks to refresh thematics and topics lists * complete Cypress tests --- src/pages/blog/page/[number].tsx | 334 +++++++++++++++++++++++++-------------- 1 file changed, 218 insertions(+), 116 deletions(-) (limited to 'src/pages/blog/page/[number].tsx') diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx index 35d4bad..ec465c2 100644 --- a/src/pages/blog/page/[number].tsx +++ b/src/pages/blog/page/[number].tsx @@ -18,6 +18,9 @@ import { PageHeader, PageBody, PageSidebar, + Spinner, + Notice, + LoadingPage, } from '../../../components'; import { convertWPThematicPreviewToPageLink, @@ -30,9 +33,12 @@ import { fetchTopicsCount, fetchTopicsList, } from '../../../services/graphql'; +import styles from '../../../styles/pages/blog.module.scss'; import type { GraphQLConnection, + Maybe, NextPageWithLayout, + Nullable, WPPostPreview, WPThematicPreview, WPTopicPreview, @@ -48,17 +54,24 @@ import { } from '../../../utils/helpers'; import { loadTranslation, type Messages } from '../../../utils/helpers/server'; import { + useArticlesList, useBreadcrumb, - usePostsList, useRedirection, + useThematicsList, + useTopicsList, } from '../../../utils/hooks'; +const renderPaginationLink: RenderPaginationLink = (pageNum) => + `${ROUTES.BLOG}/page/${pageNum}`; + type BlogPageProps = { + data: { + posts: GraphQLConnection; + thematics: GraphQLConnection; + topics: GraphQLConnection; + }; + lastCursor: Maybe>; pageNumber: number; - posts: GraphQLConnection; - thematicsList: WPThematicPreview[]; - topicsList: WPTopicPreview[]; - totalArticles: number; translation: Messages; }; @@ -66,86 +79,129 @@ type BlogPageProps = { * Blog index page. */ const BlogPage: NextPageWithLayout = ({ + data, + lastCursor, pageNumber, - posts, - thematicsList, - topicsList, - totalArticles, }) => { useRedirection({ - query: { param: 'number', value: '1' }, - redirectTo: ROUTES.BLOG, + isReplacing: true, + to: ROUTES.BLOG, + whenPathMatches: (path) => path === `${ROUTES.BLOG}/page/1`, }); - const { articles } = usePostsList({ - fallback: [posts], - fetcher: fetchPostsList, + const intl = useIntl(); + const { isFallback } = useRouter(); + const { + articles, + error, + firstNewResultIndex, + isLoading, + isLoadingMore, + isRefreshing, + hasNextPage, + loadMore, + } = useArticlesList({ + after: lastCursor, + fallback: [data.posts], perPage: CONFIG.postsPerPage, }); - const intl = useIntl(); - const title = intl.formatMessage({ - defaultMessage: 'Blog', - description: 'BlogPage: page title', - id: '7TbbIk', + const { isLoading: areThematicsLoading, thematics } = useThematicsList({ + fallback: data.thematics, + input: { first: data.thematics.pageInfo.total }, + }); + const { isLoading: areTopicsLoading, topics } = useTopicsList({ + fallback: data.topics, + input: { first: data.topics.pageInfo.total }, }); - const pageNumberTitle = intl.formatMessage( - { - defaultMessage: 'Page {number}', - id: 'zbzlb1', - description: 'BlogPage: page number', + + const messages = { + loading: { + thematicsList: intl.formatMessage({ + defaultMessage: 'Thematics are loading...', + description: 'BlogPage: loading thematics message', + id: 'y37FuH', + }), + topicsList: intl.formatMessage({ + defaultMessage: 'Topics are loading...', + description: 'BlogPage: loading topics message', + id: 'OsclKU', + }), }, - { - number: pageNumber, - } - ); - const pageTitleWithPageNumber = `${title} - ${pageNumberTitle}`; + pageTitle: intl.formatMessage( + { + defaultMessage: 'Blog - Page {number}', + description: 'BlogPage: page title with number', + id: '8xVO3Y', + }, + { + number: pageNumber, + } + ), + pagination: { + noJS: intl.formatMessage({ + defaultMessage: + "You can't load more articles without Javascript, please use the pagination instead.", + description: 'BlogPage: pagination no script message', + id: 'ZMES/E', + }), + title: intl.formatMessage({ + defaultMessage: 'Pagination', + description: 'BlogPage: pagination accessible name', + id: 'AXe1Iz', + }), + }, + seo: { + metaDesc: intl.formatMessage( + { + defaultMessage: + "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.", + description: 'BlogPage: SEO - Meta description', + id: '18h/t0', + }, + { websiteName: CONFIG.name } + ), + title: intl.formatMessage( + { + defaultMessage: + 'Blog: development, open source - Page {number} - {websiteName}', + description: 'BlogPage: SEO - Page title', + id: 'dG3sT3', + }, + { number: pageNumber, websiteName: CONFIG.name } + ), + }, + widgets: { + thematicsListTitle: intl.formatMessage({ + defaultMessage: 'Thematics', + description: 'BlogPage: thematics list widget title', + id: 'HriY57', + }), + topicsListTitle: intl.formatMessage({ + defaultMessage: 'Topics', + description: 'BlogPage: topics list widget title', + id: '2D9tB5', + }), + }, + }; + const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ - title: pageNumberTitle, + title: messages.pageTitle, url: `${ROUTES.BLOG}/page/${pageNumber}`, }); - const { asPath } = useRouter(); - const page = { - title: `${pageTitleWithPageNumber} - ${CONFIG.name}`, - url: `${CONFIG.url}${asPath}`, - }; - const pageDescription = intl.formatMessage( - { - defaultMessage: - "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.", - description: 'BlogPage: SEO - Meta description', - id: '18h/t0', - }, - { websiteName: CONFIG.name } - ); const webpageSchema = getWebPageSchema({ - description: pageDescription, + description: messages.seo.metaDesc, locale: CONFIG.locales.defaultLocale, - slug: asPath, - title, + slug: ROUTES.BLOG, + title: messages.pageTitle, }); const blogSchema = getBlogSchema({ isSinglePage: false, locale: CONFIG.locales.defaultLocale, - slug: asPath, + slug: ROUTES.BLOG, }); const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); - const thematicsListTitle = intl.formatMessage({ - defaultMessage: 'Thematics', - description: 'BlogPage: thematics list widget title', - id: 'HriY57', - }); - - const topicsListTitle = intl.formatMessage({ - defaultMessage: 'Topics', - description: 'BlogPage: topics list widget title', - id: '2D9tB5', - }); - const renderPaginationLink: RenderPaginationLink = useCallback( - (pageNum) => `${ROUTES.BLOG}/page/${pageNum}`, - [] - ); const renderPaginationLabel: RenderPaginationItemAriaLabel = useCallback( ({ kind, pageNumber: number, isCurrentPage }) => { switch (kind) { @@ -191,27 +247,21 @@ const BlogPage: NextPageWithLayout = ({ [intl] ); - const paginationAriaLabel = intl.formatMessage({ - defaultMessage: 'Pagination', - description: 'BlogPage: pagination accessible name', - id: 'AXe1Iz', - }); + if (isFallback) return ; - const blogPageArticles = articles?.flatMap((p) => - p.edges.map((edge) => edge.node) - ); + const pageUrl = `${CONFIG.url}${ROUTES.BLOG}`; return ( - {page.title} + {messages.seo.title} {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} - - + + {/*eslint-disable-next-line react/jsx-no-literals -- Content allowed */} - - + +